package com.maycur.ctrip.service.impl;

import com.maycur.ctrip.service.RedisService;
import com.maycur.ctrip.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;


@Service
public class RedisServiceImpl implements RedisService {

    public static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);
    @Autowired
    protected StringRedisTemplate stringRedisTemplate;

    /**
     * WARNING: This method will clear redis current database data!
     */
    @Override
    public void flushDB() {
        stringRedisTemplate.getConnectionFactory().getConnection().flushDb();
    }


    @Override
    public boolean exists(final String key) {
        return stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.exists(key.getBytes());
            }
        });
    }

    @Override
    public Set<String> setKeys(final String pattern) {
        return stringRedisTemplate.keys(pattern);
    }

    @Override
    public String ping() {
        return stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.ping();
            }
        });
    }

    @Override
    public void saveValueWithExpireTime(final byte[] key, final byte[] value, final long expireTime) {
        stringRedisTemplate.execute(new RedisCallback<String>() {
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(key, value);
                if (expireTime > 0) {
                    connection.expire(key, expireTime);
                }
                return null;
            }
        });
    }

    @Override
    public void saveValue(byte[] key, byte[] value) {
        this.saveValueWithExpireTime(key, value, 0L);
    }


    /**
     * 保存String value到key里面
     */
    @Override
    public void saveValue(final String key, final String value) {
        saveValue(stringRedisTemplate.getStringSerializer().serialize(key),
            stringRedisTemplate.getStringSerializer().serialize(value));
    }

    /**
     * 保存String value到key里面, 并且带过期时间
     */
    @Override
    public void saveValueWithExpireTime(String key, String value, long expireTime, TimeUnit timeUnit) {
        saveValue(key, value);

        stringRedisTemplate.expire(key, expireTime, timeUnit);
    }

    /**
     * 以String的形式返回key里面的value值
     */
    @Override
    public String getValue(final String key) {
        return stringRedisTemplate.execute(new RedisCallback<String>() {

            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keyByte = stringRedisTemplate.getStringSerializer().serialize(key);
                if (connection.exists(keyByte)) {
                    byte[] value = connection.get(keyByte);
                    return stringRedisTemplate.getStringSerializer().deserialize(value);
                }
                return null;
            }
        });
    }

    /**
     * 以Json的格式保存泛型对象到key里面
     */
    @Override
    public <T> void saveGeneric(final String key, final T value) {
        if (key == null || value == null) {
            return;
        }
        stringRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(value.getClass()));

        stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(stringRedisTemplate.getStringSerializer().serialize(key), ((Jackson2JsonRedisSerializer<T>)stringRedisTemplate.getValueSerializer()).serialize(value));
                return null;
            }
        });
    }

    /**
     * 以Json的格式保存泛型对象到key里面, 并且带过期时间
     */
    @Override
    public<T> void saveGenericWithExpireTime(String key, T obj, long expireTime, TimeUnit timeUnit) {
        saveGeneric(key, obj);

        stringRedisTemplate.expire(key, expireTime, timeUnit);

    }

    /**
     * 获得key里面存储的value, 并通过传入的类解析为实际对象
     */
    @Override
    public <T> T getGeneric(String key, Class<T> valueClass) {
        String value = getValue(key);
        if (value == null) {
            return null;
        }
        return (new Jackson2JsonRedisSerializer<>(valueClass)).deserialize(value.getBytes());
    }

    /**
     * 以列表的形式保存泛型对象列表到key里面
     */
    @Override
    public <T> void saveGenericList(final String key, final List<T> list, final Class<T> clazz) {
        if (list.isEmpty()) {
            return;
        }
        stringRedisTemplate.execute(new RedisCallback<List<T>>() {

            @Override
            public List<T> doInRedis(RedisConnection connection) throws DataAccessException {

                stringRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(clazz));

                byte[][] values = new byte[list.size()][];
                int index = 0;
                for (T obj : list) {
                    byte[] value =
                        ((Jackson2JsonRedisSerializer<T>) stringRedisTemplate.getValueSerializer()).serialize(obj);

                    values[index] = value;
                    index++;
                }
                connection.rPush(stringRedisTemplate.getStringSerializer().serialize(key), values);//只会加在原来list的右边

                return null;
            }
        });
    }

    /**
     * 以列表的形式保存泛型对象列表到key里面,并带过期时间
     */
    @Override
    public <T> void saveGenericListWithExpireTime(String key, List<T> list, Class<T> clazz, long expireTime, TimeUnit timeUnit) {
        saveGenericList(key, list, clazz);

        stringRedisTemplate.expire(key, expireTime, timeUnit);
    }

    /**
     * 获得key里面存储的value, 并通过传入的类解析为实际对象列表
     */
    @Override
    public <T> List<T> getGenericList(final String key, final Class<T> clazz) {
        return stringRedisTemplate.execute(new RedisCallback<List<T>>() {

            @Override
            public List<T> doInRedis(RedisConnection connection) throws DataAccessException {
                stringRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(clazz));
                List<T> list = new ArrayList<>();
                List<byte[]> values =
                    connection.lRange(stringRedisTemplate.getStringSerializer().serialize(key), 0, -1);
                for (byte[] value : values) {
                    T obj = ((Jackson2JsonRedisSerializer<T>) stringRedisTemplate.getValueSerializer())
                        .deserialize(value);
                    list.add(obj);
                }

                return list;
            }
        });
    }

    /**
     * 以JSON的形式保存泛型对象列表到key里面
     */
    @Override
    public <T> void saveJsonGenericList(final String key, final List<T> list) {
        if (list.isEmpty()) {
            return;
        }
        stringRedisTemplate.execute(new RedisCallback<List<T>>() {
            @Override
            public List<T> doInRedis(RedisConnection connection) throws DataAccessException {
                String value = JsonUtil.toString(list);
                saveValue(key, value);
                return null;
            }
        });
    }

    /**
     * 以JSON的形式保存泛型对象列表到key里面, 并带过期时间
     */
    @Override
    public <T> void saveJsonGenericListWithExpireTime(String key, List<T> list, long expireTime, TimeUnit timeUnit) {
        saveJsonGenericList(key, list);

        stringRedisTemplate.expire(key, expireTime, timeUnit);
    }

    /**
     * 获得key里面存储的JSON value, 并通过传入的类解析为实际对象列表
     */
    @Override
    public <T> List<T>  getJsonGenericList(final String key, final Class<T> clazz) {
        return stringRedisTemplate.execute(new RedisCallback<List<T>>() {
            @Override
            public List<T> doInRedis(RedisConnection connection) throws DataAccessException {
                List<T> list = new ArrayList<T>();
                byte[] keyByte = stringRedisTemplate.getStringSerializer().serialize(key);
                if (connection.exists(keyByte)) {
                    byte[] valueByte = connection.get(keyByte);
                    String s = stringRedisTemplate.getStringSerializer().deserialize(valueByte);
                    list = JsonUtil.toList(s, clazz);
                }
                return list;
            }
        });
    }

    @Override
    public void remove(final String key) {
        stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.del(stringRedisTemplate.getStringSerializer().serialize(key));
                return null;
            }
        });

    }

    /**
     * @param keys
     * @return
     */
    @Override
    public void mRemove(String... keys) {
        for(String key: keys) {
            remove(key);
        }
    }


    @Override
    public void hSaveValueWithExpireTime(final byte[] key, final byte[] field, final byte[] value, final long expireTime) {
        stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.hSet(key, field, value);
                if (expireTime > 0) {
                    connection.expire(key, expireTime);
                }
                return null;
            }
        });
    }


    @Override
    public void hSaveValue(byte[] key, final byte[] field, final byte[] value) {
        hSaveValueWithExpireTime(key, field, value, 0);
    }

    /**
     * 保存String value到key(key对应的value是map)->field中
     */
    @Override
    public void hSaveValue(String key, String field, String value) {
        this.hSaveValue(stringRedisTemplate.getStringSerializer().serialize(key),
            stringRedisTemplate.getStringSerializer().serialize(field),
            stringRedisTemplate.getStringSerializer().serialize(value));
    }


    /**
     * 保存String value到key(key对应的value是map)->field中, 并且带过期时间
     */
    @Override
    public void hSaveValueWithExpireTime(String key, String field, String value, long expireTime, TimeUnit timeUnit) {
        this.hSaveValue(key, field, value);
        stringRedisTemplate.expire(key, expireTime, timeUnit);
    }

    /**
     * 以String的形式返回key->field的value值
     */
    @Override
    public String hGetValue(final String key, final String field) {
        return stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keyByte = stringRedisTemplate.getStringSerializer().serialize(key);
                byte[] fieldByte = stringRedisTemplate.getStringSerializer().serialize(field);
                if (connection.hExists(keyByte, fieldByte)) {
                    byte[] value = connection.hGet(stringRedisTemplate.getStringSerializer().serialize(key),
                        stringRedisTemplate.getStringSerializer().serialize(field));
                    return stringRedisTemplate.getStringSerializer().deserialize(value);
                } else {
                    return null;
                }

            }
        });
    }


    /**
     * 以JSON格式保存T对象到key(key对应的value是map)->field中, 并且带过期时间
     */
    @Override
    public <T> void hSaveGenericWithExpireTime(String key, String field, T value, long expireTime) {
        stringRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(value.getClass()));

        hSaveValueWithExpireTime(stringRedisTemplate.getStringSerializer().serialize(key),
            stringRedisTemplate.getStringSerializer().serialize(field),
            ((Jackson2JsonRedisSerializer<T>) stringRedisTemplate.getValueSerializer()).serialize(value),
            expireTime);
    }

    /**
     * 以JSON格式保存T对象到key(key对应的value是map)->field中
     */
    @Override
    public <T> void hSaveGeneric(String key, String field, T value) {
        hSaveGenericWithExpireTime(key, field, value, 0);
    }

    /**
     * 获得key->field里面存储的JSON value, 并通过传入的类解析为实际对象返回
     */
    @Override
    public <T> T hGetGeneric(final String key, final String field, final Class<T> clazz) {
        return stringRedisTemplate.execute(new RedisCallback<T>() {

            @Override
            public T doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keyByte = stringRedisTemplate.getStringSerializer().serialize(key);
                byte[] fieldByte = stringRedisTemplate.getStringSerializer().serialize(field);
                if (connection.hExists(keyByte, fieldByte)) {
                    stringRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(clazz));
                    byte[] value = connection.hGet(stringRedisTemplate.getStringSerializer().serialize(key),
                        stringRedisTemplate.getStringSerializer().serialize(field));
                    return ((Jackson2JsonRedisSerializer<T>) stringRedisTemplate.getValueSerializer())
                        .deserialize(value);
                } else {
                    return null;
                }
            }
        });
    }

    /**
     * 以JSON格式保存T对象列表到key(key对应的value是map)->field中, 并带过期时间
     */
    @Override
    public <T> void hSaveJsonGenericListWithExpireTime(String key, String field, List<T> list, long expireTime) {
        String value = JsonUtil.toString(list);
        hSaveValueWithExpireTime(stringRedisTemplate.getStringSerializer().serialize(key),
            stringRedisTemplate.getStringSerializer().serialize(field),
            stringRedisTemplate.getStringSerializer().serialize(value), expireTime);
    }

    /**
     * 以JSON格式保存T对象列表到key(key对应的value是map)->field中
     */
    @Override
    public <T> void hSaveJsonGenericList(String key, String field, List<T> list) {
        hSaveJsonGenericListWithExpireTime(key, field, list, 0);
    }

    /**
     * 从key(key对应的value是map)->field中获得JSON格式的value, 并通过传入类解析为对象列表
     */
    @Override
    public <T> List<T> hGetJsonGenericList(final String key, final String field, final Class<T> clazz) {
        return stringRedisTemplate.execute(new RedisCallback<List<T>>() {

            @Override
            public List<T> doInRedis(RedisConnection connection) throws DataAccessException {
                List<T> list = new ArrayList<T>();
                byte[] keyByte = stringRedisTemplate.getStringSerializer().serialize(key);
                byte[] fieldByte = stringRedisTemplate.getStringSerializer().serialize(field);
                if (connection.hExists(keyByte, fieldByte)) {
                    byte[] valueByte = connection.hGet(keyByte, fieldByte);
                    String s = stringRedisTemplate.getStringSerializer().deserialize(valueByte);
                    list = JsonUtil.toList(s, clazz);
                }
                return list;
            }
        });
    }

    @Override
    public void hRemove(final String key, final String field) {
        stringRedisTemplate.execute(new RedisCallback<Object>() {

            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.hDel(stringRedisTemplate.getStringSerializer().serialize(key),
                    stringRedisTemplate.getStringSerializer().serialize(field));

                return null;
            }
        });
    }

    @Override
    public void hmRemove(String key, String... fields) {
        for(String field: fields) {
            this.hRemove(key, field);
        }
    }

    /**
     * key下面存储的是map, 通过key返回下面整个map的数据
     */
    @Override
    public Map<String, String> hGetAllByKey(final String key) {
        return stringRedisTemplate.execute(new RedisCallback<Map>() {

            @Override
            public Map doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keyByte = stringRedisTemplate.getStringSerializer().serialize(key);
                if (connection.exists(keyByte)) {
                    Map<byte[], byte[]> maps = connection.hGetAll(keyByte);
                    Map result = new HashMap();

                    for (byte[] mapKey : maps.keySet()) {
                        //System.out.println("key= "+ mapKey + " and value= " + maps.get(mapKey));
                        String field = stringRedisTemplate.getStringSerializer().deserialize(mapKey);
                        String fieldValue = stringRedisTemplate.getStringSerializer().deserialize(maps.get(mapKey));

                        result.put(field, fieldValue);
                    }

                    return result;
                }
                return null;
            }
        });
    }

}
